﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Data;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using System.Reflection;

using Noris.Schedule.Support.Sql;
using Noris.Schedule.Support.DataFace;
using Noris.Schedule.Support.Green;

namespace Noris.Schedule.Support.Data
{
	#region INTERFACES
	/// <summary>
	/// Interface, který deklaruje přítomnost property Stock a Source { get; }
	/// </summary>
	public interface IStockSource
	{
		int Stock { get; }
		int Source { get; }
	}
	/// <summary>
	/// Interface, který deklaruje přítomnost property Source, Variation, OrderRelation { get; }
	/// </summary>
	public interface ISourceVarOrd
	{
		int Source { get; }
		SqlInt32 Variation { get; }
		SqlInt32 OrderRelation { get; }
	}
	/// <summary>
	/// Interface, který deklaruje schopnost objektu hrát roli záznamu na vizuální ploše.
	/// Záznam lze resetovat, duplikovat, načítat, ukládat, má své číslo třídy a číslo záznamu.
	/// </summary>
	public interface IRecord : IRecordKey, IPersistable
	{
		void Reset();
		/// <summary>
		/// Vytvoří a vrátí duplikát sebe sama, ale s novým/prázdným číslem záznamu
		/// </summary>
		/// <returns></returns>
		object CreateNewDuplicate(bool createTemporaryId);
	}
	/// <summary>
	/// Interface, který deklaruje přítomnost property, které specifikují Green záznam bez položky (třída, číslo záznamu)
	/// </summary>
	public interface IRecordKey
	{
		/// <summary>Číslo třídy, povinně</summary>
		int ClassNumber { get; }
		/// <summary>Číslo subjektu/nonsubjektu</summary>
		int RecordNumber { get; }
	}
    /// <summary>
    /// Interface, který deklaruje přítomnost property ReferNazev
    /// </summary>
    public interface IReferNazev
    {
        /// <summary>
        /// Reference a název záznamu
        /// </summary>
        string ReferNazev { get; }
    }
	/// <summary>
	/// Interface, který deklaruje schopnost objektu být načten a uložen do databáze.
	/// Načítání se děje instanční (!) metodou Load(int recordNumber), ukládání se děje instanční metodou Save().
	/// Uživatel nemusí řešit logiku třídy a jejího rozložení v databázi.
	/// </summary>
	public interface IPersistable
	{
		/// <summary>
		/// Do existující instance načte data z databáze
		/// </summary>
		/// <param name="recordNumber">Číslo záznamu, který je třeba načíst</param>
		void Load(int recordNumber);
		/// <summary>
		/// Uloží data z této instance do databáze
		/// </summary>
		void Save();
	}
	#endregion
	#region DELEGATES
	/// <summary>
	/// Delegát specifikující akci se záznamem
	/// </summary>
	/// <param name="record"></param>
	public delegate void RecordAction(RecordCls record);
	#endregion
	#region RecordCls : Základní třída pro jednotlivý záznam.
	/// <summary>
	/// RecordCls : Základní třída pro jednotlivý záznam.
	/// Zajišťuje prostor pro virtuální metody, zajišťuje číslování záznamů do jednotné Temporary řady.
	/// </summary>
    public abstract class RecordCls : IRecord, IDbRecord, IReferNazev, IDisposable
	{
		#region KONSTRUKTORY
		/// <summary>
		/// Implicitní konstruktor, zajistí číslování záznamů do jednotné Temporary řady
		/// </summary>
		public RecordCls()
		{
			this._Reset();
			_AssignTemporaryId();
		}
		/// <summary>
		/// Konstruktor s načítáním dat z databáze
		/// </summary>
		/// <param name="recordNumber">Číslo záznamu, který je třeba načíst</param>
		public RecordCls(int recordNumber)
		{
			this._Reset();
			if (recordNumber > 0)
				this.Load(recordNumber);
		}
		/// <summary>
		/// Explicitní konstruktor, dovolí zvolit, zda se pro tento záznam bude generovat Temporary ID
		/// </summary>
		/// <param name="createTemporaryId">true pokud se pro záznam má vytvořit Temporary ID. Default (false) = nevytvoří se, bude mít RecordNumber = 0.</param>
		public RecordCls(bool createTemporaryId)
		{
			this._Reset();
			if (createTemporaryId)
			{
				_AssignTemporaryId();
			}
		}
		/// <summary>
		/// Privátní Reset. Je volaný z každého konstruktoru. Pozor na virtuální metody. Volá aplikační Reset.
		/// </summary>
		private void _Reset()
		{
            this.IsDirty = false;
			this.Reset();
		}
		#endregion
		#region DISPOSE
		/// <summary>
		/// IDisposable.Dispose
		/// </summary>
		void IDisposable.Dispose()
		{
			if (!_Disposed)
			{
				_DisposingInternalBefore();
				Disposing(RecordClsEventArgs.Empty);
				_DisposingInternalAfter();
			}
		}
		/// <summary>
		/// Systémový Dispose, volaný před aplikačním Dispose
		/// </summary>
		private void _DisposingInternalBefore()
		{
		}
		/// <summary>
		/// Systémový Dispose, volaný po aplikačním Dispose
		/// </summary>
		private void _DisposingInternalAfter()
		{
			_Disposed = true;
		}
		/// <summary>
		/// true je poté, kdy proběhlo Dispose.
		/// </summary>
		public bool Disposed { get { return _Disposed; } }
		private bool _Disposed = false;
		#endregion
		#region PUBLIC PROPERTY
		/// <summary>
		/// Kombinace čísla třídy (ClassNumber) a čísla záznamu (RecordNumber).
		/// Pokud je RecordNumber == CisloObjektu, pak GID obsahuje záporné číslo třídy = jde o položkový GID.
		/// </summary>
		public virtual GID GId
		{
			get
			{
				int recordNumber = RecordNumber;
				if (recordNumber != 0 && recordNumber == _cislo_objektu)
					return new GID(-ClassNumber, recordNumber);
				return new GID(ClassNumber, recordNumber);
			}
		}
		/// <summary>
		/// Číslo záznamu. Implicitně vrací _cislo_subjektu. Je možno přepsat na potomkovi, tak aby vracel číslo nonsubjektu / číslo objektu.
		/// </summary>
		public virtual int RecordNumber { get { return _cislo_subjektu; } }
		/// <summary>IDbRecord.RecordNumber : musí konkrétně implementovat set, protože DbLayer po uložení nového záznamu (kde RecordNumber == 0) do něj vloží přidělený klíč</summary>
		int IDbRecord.RecordNumber { get { return this.RecordNumber; } set { this._cislo_subjektu = value; } }
		/// <summary>
		/// Číslo subjektu. Vrací číslo subjektu i u položkových tříd.
		/// </summary>
		public virtual int CisloSubjektu { get { return _cislo_subjektu; } }
		/// <summary>
		/// Číslo objektu. Vrací číslo objektu, u tříd bez položek obsahuje 0.
		/// </summary>
		public virtual int CisloObjektu { get { return _cislo_objektu; } }
		/// <summary>
		/// Číslo pořadače
		/// </summary>
        public virtual int FolderNumber { get { return _cislo_poradace; } protected set { _cislo_poradace = value; IsDirty = true; } }
		/// <summary>
		/// Číslo třídy, přepisuje se na potomkovi
		/// </summary>
		public abstract int ClassNumber { get; }
		/// <summary>
		/// Reference záznamu. Implicitně vrací _reference_subjektu.
		/// </summary>
        public virtual string Reference { get { return _reference_subjektu; } protected set { _reference_subjektu = value; IsDirty = true; } }
		/// <summary>
		/// Reference záznamu, nebo String.Empty pokud Reference je null.
		/// </summary>
		public virtual string ReferenceNn { get { return (Reference ?? String.Empty); } }
		/// <summary>
		/// Název záznamu. Implicitně vrací _nazev_subjektu.
		/// </summary>
        public virtual string Nazev { get { return _nazev_subjektu; } protected set { _nazev_subjektu = value; IsDirty = true; } }
		/// <summary>
		/// Název záznamu, nebo String.Empty pokud Nazev je null.
		/// </summary>
		public virtual string NazevNn { get { return (Nazev ?? String.Empty); } }
		/// <summary>
		/// Reference a Název záznamu, oddělené ": ". Anebo bez oddělení dvojtečkou, pokud ReferenceNn nebo NazevNn je String.Empty.
		/// Vhodné pro snadný výpis reference a názvu.
		/// </summary>
		public virtual string ReferNazev { get { return GetReferNazev(ReferenceNn, NazevNn); } }
		/// <summary>
		/// Vrátí referenci a název, oddělené ": ". Anebo bez oddělení dvojtečkou, pokud ReferenceNn nebo NazevNn je String.Empty.
		/// Vhodné pro snadný výpis reference a názvu.
		/// </summary>
		/// <param name="refer">Reference. Může být i null. Pokud je null, nebo prázdná, vrací se jen název.</param>
		/// <param name="nazev">Název. Může být i null. Pokud je null, nebo prázdný, vrací se jen reference.</param>
		/// <returns></returns>
		public static string GetReferNazev(string refer, string nazev)
		{
			string referNn = (refer == null ? String.Empty : refer);
			string nazevNn = (nazev == null ? String.Empty : nazev);
			if (referNn == String.Empty)
				return nazevNn;
			if (nazevNn == String.Empty)
				return referNn;
			return referNn + ": " + nazevNn;
		}
        /// <summary>
        /// Příznak, že záznam obsahuje data změněná oproti původnímu stavu.
        /// Registruje změny datových property v jejich jednotlivých set accessorech.
        /// </summary>
        public bool IsDirty { get; protected set; }
        /// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "{" + this.GetString() + "}";
		}
		/// <summary>
		/// Tvorba obsahu vizualizace, bez krajních závorek.
		/// Třída RecordCls vrací vrací "#Record = Reference: Nazev"
		/// </summary>
		/// <returns></returns>
		public virtual string GetString()
		{
			return "#" + this.RecordNumber.ToString() + " = " + this.ReferNazev;
		}
		#endregion
		#region PROTECTED FIELDS = data, která se plní při načítání
		protected int _cislo_subjektu;
		protected int _cislo_objektu;
		protected int _cislo_nonsubjektu;
		protected int _cislo_poradace;
		protected string _reference_subjektu;
		protected string _nazev_subjektu;
		#endregion
		#region FILL FROM READER
		/// <summary>
		/// Naplní data do objektu this (systémové atributy: cislo_subjektu, cislo_nonsubjektu, cislo_objektu, cislo_poradace, reference_subjektu, nazev_subjektu)
		/// z předaného readeru za použití objektu mapování.
		/// Čtení touto metodou je cca 6x rychlejší než čtení generickou metodou.
		/// Metoda má vracet true, pokud převzala všechna data. 
		/// Metoda base vrací false: bez přepsání na potomkovi nejsou data načtena (a načtou se genericky).
		/// Je nanejvýše účelné, aby tak provedla, protože pokud vrátí false, pak se po jejím skončení provede generické ukládání dat (což je 6x pomalejší).
		/// Typické uspořádání na třídě override:
		/// base.FillFromReader(reader, map);
		/// sada příkazů _field = (int)map.FillDataIntoField«int»("field", reader);
		/// return true;
        /// Pozor: z této metody na třídě RecordCls se volá virtuální metoda this.FillFromReaderExtended(), která je určená pro extendery tříd.
		/// </summary>
		/// <param name="reader">Vstupní DataReader</param>
		/// <param name="map">Mapování dat z readeru na fields tohoto objektu</param>
		/// <returns>true = data byla převzata / false = data nepřevzata</returns>
		public virtual bool FillFromReader(SqlDataReader reader, FieldMapperLoad map)
		{
			_cislo_subjektu = (int)map.FillDataIntoField<int>("cislo_subjektu", reader);
			_cislo_nonsubjektu = (int)map.FillDataIntoField<int>("cislo_nonsubjektu", reader);
			_cislo_objektu = (int)map.FillDataIntoField<int>("cislo_objektu", reader);
			_cislo_poradace = (int)map.FillDataIntoField<int>("cislo_poradace", reader);
			_reference_subjektu = (string)map.FillDataIntoField<string>("reference_subjektu", reader);
			_nazev_subjektu = (string)map.FillDataIntoField<string>("nazev_subjektu", reader);
            this.FillFromReaderExtended(reader, map);
			return false;
		}
        /// <summary>
        /// Načtení dat z readeru v rámci extenderu třídy.
        /// Základní fields načítá základní metoda třídy, ale fields rozšířené (nad rámec třídy, ale načítané z databáze) načítá extender v této metodě.
        /// Důvodem je to, aby se strojově generovaný kód (typicky v DataFaces) třídy nemusel ručně modifikovat.
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="map"></param>
        public virtual void FillFromReaderExtended(SqlDataReader reader, FieldMapperLoad map)
        { }
		/// <summary>
		/// Metoda, ve které datový objekt připraví podklady pro uložení svých dat do databáze.
		/// Objekt svoje proměnné vloží do exportní mapy prostřednictvím metod map.AddTable(); map.AddColumn();
		/// Pokud objekt chce plnit více tabulek, plní je do mapy postupně, vždy v pořadí {tabulka, sloupce tabulky; další tabulka; sloupce}.
		/// Po naplnění mapy se vrací true.
		/// </summary>
		/// <param name="map"></param>
		/// <returns></returns>
		public virtual bool PrepareSaveData(FieldMapperSave map)
		{
			return false;
		}
		#endregion
		#region LOAD & SAVE, SaveCollections()
        /// <summary>
        /// Resetuje příznak IsDirty.
        /// Tuto metodu používejte zcela výjimečně, aplikační kód se po jejím použití bude domnívat, že záznam není změněn od stavu v databázi.
        /// </summary>
        public virtual void ResetDirty()
        {
            this.IsDirty = false;
        }
        /// <summary>
        /// Do existujícího objektu znovu načte svoje data z databáze.
        /// Má význam pouze u objektů, do nichž je načten záznam z databáze (jehož RecordNumber není 0).
        /// Samozřejmě tím dojde ke ztrátě všech nynějších dat v objektu.
        /// Pokud je metoda volaná na objektu, který nepochází z databáze, pak je obsah objektu vynulován.
        /// </summary>
        public virtual void Refresh()
        {
            this.Load(this.RecordNumber);
        }
        /// <summary>
		/// Do existujícího objektu načte data z databáze
		/// </summary>
		/// <param name="recordNumber">Číslo záznamu, který je třeba načíst</param>
		public virtual void Load(int recordNumber)
		{
			string sql = GetCacheSelect() + " = " + SqlFormat.ToSql(recordNumber);
            this.IsDirty = false;
			Db_Layer.LoadIntoRecord((IDbRecord)this, sql);
			LoadAfter(RecordClsEventArgs.Empty);
		}
		/// <summary>
		/// Zajistí uložení dat tohoto záznamu
		/// </summary>
		public virtual void Save()
		{
			this.CheckBeforeSave(RecordClsEventArgs.Empty);
            if (this.RecordNumber <= 0)
                this.SaveBeforeNewRecord(RecordClsEventArgs.Empty);
            this.SaveBefore(RecordClsEventArgs.Empty);
            Db_Layer.SaveRecord((IDbRecord)this);
            this.IsDirty = false;
			this.SaveAfter(RecordClsEventArgs.Empty);
		}
		/// <summary>
		/// Zajistí update uvedených sloupců.
		/// Záznam musí existovat (tj. jeho RecordNumber musí být kladné).
		/// Seznam sloupců může být předán jako pole stringů, nebo jako jeden string s jednotlivými columns oddělenými čárkou.
		/// Pokud tento záznam ukládá data do více tabulek, a je třeba specifikovat jen některou z nich, 
		/// může dbcolumn obsahovat jméno tabulky (například "reference_subjektu, lcs.product_order.nazev_subjektu", atd.)
		/// Seznam se odkazuje na jména sloupců, nikoli jména property nebo fields v objektu. Vyhledání odpovídajících dat je case-insensitive.
		/// Pokud bude v seznamu uveden sloupec, který neexistuje, dojde k chybě.
		/// Pokud bude požadován Update pro záznam, který dosud neexistuje (RecordNumber není kladné), dojde k chybě.
		/// </summary>
		/// <param name="columnList"></param>
		public virtual void UpdateColumns(params string[] columnList)
		{
			this.CheckBeforeSave(RecordClsEventArgs.Empty);
            if (this.RecordNumber <= 0)
                this.SaveBeforeNewRecord(RecordClsEventArgs.Empty);
            this.SaveBefore(RecordClsEventArgs.Empty);
            Db_Layer.UpdateColumns((IDbRecord)this, columnList);
			SaveAfter(RecordClsEventArgs.Empty);
		}
		/// <summary>
		/// Dovolí uložit jednu nebo více kolekcí položek, které jsou samy uložitelné
		/// </summary>
		/// <param name="collections">Jednotlivé kolekce záznamů, které se mají ukládat</param>
		protected void SaveCollections(params IEnumerable<IPersistable>[] collections)
		{
			for (int c = 0; c < collections.Length; c++)
			{
				IEnumerable<IPersistable> collection = collections[c];
				if (collection == null) continue;
				foreach (IPersistable item in collection)
				{
					if (item == null) continue;
					item.Save();
				}
			}
		}
        /// <summary>
        /// Vrací informaci o tom, zda nyní pro tento záznam existuje odpovídající věta v databázi.
        /// Pokud this.
        /// </summary>
        /// <returns></returns>
        public virtual bool RecordNowExists()
        {
            return this.RecordNowExists(null);
        }
        /// <summary>
        /// Vrací informaci o tom, zda nyní pro tento záznam existuje odpovídající věta v databázi.
        /// Pokud this.
        /// </summary>
        /// <returns></returns>
        public virtual bool RecordNowExists(DataConnector connector)
        {
            if (this.RecordNumber <= 0) return false;

            FieldMapperSave map = new FieldMapperSave();
            bool prepared = this.PrepareSaveData(map);
            if (!prepared) return false;

            if (map.TableDataList.Count == 0) return false;
            FieldMapperOneTableCls table = map.TableDataList[0];

            string sql = table.GetCommandExists(this.RecordNumber);
            if (connector == null) connector = DataConnect.CurrentConnect;
            int recordNumber = Db_Layer.GetItem<int>(sql, connector);

            return (recordNumber == this.RecordNumber);
        }
		#endregion
		#region HÁČKY A OVERRIDES
		/// <summary>
		/// Reset instance. Je volaný z každého konstruktoru. 
		/// Pozor, je to virtuální metoda. V této chvíli ještě neproběhl konstruktor potomka!!!
		/// </summary>
		public virtual void Reset()
		{ }
		/// <summary>
		/// Vytvoří a vrátí duplikát sebe sama, ale s novým/prázdným číslem záznamu.
		/// Pokud je požádáno o přidělení temporary record number (parametr createTemporaryId = true), 
		/// pak nový záznam dostane číslo záznamu záporné v postupné řadě. Lze tak záznam používat v indexech a vázat na něj vztahy.
		/// </summary>
		/// <param name="createTemporaryId">Požadavek na přidělení temporary klíče.</param>
		/// <returns></returns>
		public virtual object CreateNewDuplicate(bool createTemporaryId)
		{
			object duplicate = this.MemberwiseClone();
			int recordNumber = 0;
			if (createTemporaryId)
				recordNumber = _GetTemporaryId();
			((IDbRecord)duplicate).RecordNumber = recordNumber;
			return duplicate;
		}
		/// <summary>
		/// Načítání dalších dat třídy, kromě skalárních hodnot (jednoduché proměnné uložené do jednoho řádku hlavní tabulky).
		/// Prostor pro načtení dat do navázaných kolekcí.
		/// </summary>
		public virtual void LoadAfter(RecordClsEventArgs args)
		{ }
		/// <summary>
		/// Kontrola záznamu před uložením.
		/// Je volána v rámci metody Save, před zahájením ukládání dat.
		/// </summary>
		public virtual void CheckBeforeSave(RecordClsEventArgs args)
		{ }
        /// <summary>
        /// Před provedením uložení dat nového záznamu do databáze (tj. pokud RecordNumber není kladné). 
        /// Po kontrole záznamu. Před SaveBefore().
        /// </summary>
        /// <param name="args"></param>
        public virtual void SaveBeforeNewRecord(RecordClsEventArgs args)
        { }
		/// <summary>
		/// Před provedením uložení dat do databáze.
        /// Po kontrole záznamu. Po SaveBeforeNewRecord().
		/// </summary>
		public virtual void SaveBefore(RecordClsEventArgs args)
		{ }
		/// <summary>
		/// Ukládání dalších dat třídy, kromě skalárních hodnot (jednoduché proměnné uložené do jednoho řádku hlavní tabulky).
		/// Prostor pro uložení všech položek všech kolekcí.
		/// Kolekce je možno typicky ukládat jednoduchým vyvoláním metody this.SaveCollections(params IEnumerable(IPersistable)).
		/// </summary>
		public virtual void SaveAfter(RecordClsEventArgs args)
		{ }
		/// <summary>
		/// Instance třídy se uvolňuje z paměti.
		/// Je voláno pouze jedenkrát, na konci bloku using.
		/// </summary>
		protected virtual void Disposing(RecordClsEventArgs args)
		{ }
		#endregion
		#region GENEROVÁNÍ REFERENCE A NÁZVU
		/// <summary>
		/// Resetuje záznam pro pořadač = vepíše do něj referenci dle definice pořadače
		/// </summary>
		/// <param name="folderNumber"></param>
		public void ResetForFolder(int folderNumber)
		{
			FolderInfo folderInfo = Repository.Folders[folderNumber];
			if (folderInfo == null)
				Throw.AplError(MessageInfo.Get("Záznam nelze přiřadit do pořadače %0: pořadač s tímto číslem neexistuje.", folderNumber));
			if (folderInfo.CisloTridy != this.ClassNumber)
				Throw.AplError(MessageInfo.Get("Záznam nelze přiřadit do pořadače %0: pořadač je určen pro třídu %1, ale tento záznam (%2) patří do třídy %3.", folderNumber, folderInfo.CisloTridy, this.GetType().Name, this.ClassNumber));

			this.Reference = folderInfo.CreateNewReference();
			this.FolderNumber = folderNumber;
		}
		#endregion
		#region STATIC GENERÁTORY SELECTU
		/// <summary>
		/// SQL SELECT pro načtení columnů, odpovídajících této struktuře. Pozor: neobsahuje filtr ani order.
		/// </summary>
		public static string SqlSelect { get { return null; } }
		/// <summary>
		/// Jméno sloupce, který obsahuje klíč záznamu, určeno pro filtry.
		/// </summary>
		public static string SqlRecord { get { return null; } }
		/// <summary>
		/// Generátor SELECTU pro načítání do CACHE
		/// </summary>
		/// <returns></returns>
		public virtual string GetCacheSelect()
		{
			return null;
		}
		#endregion
		#region STATIC PODPORA TVORBY NOVÝCH ZÁZNAMŮ
		/// <summary>
		/// Do tohoto objektu přiřadí nové Temporary ID
		/// </summary>
		private void _AssignTemporaryId()
		{
			((IDbRecord)this).RecordNumber = _GetTemporaryId();
		}
		/// <summary>
		/// Vrátí Temporary ID pro nově vznikající záznam. Vrací postupně záporná čísla od -1 do -2 147 483 648.
		/// Po překročení krajní hodnoty začíná zase od -1: předpokládá se, že celou číselnou řadu nevyčerpala jedna funkce, 
		/// ale že jde o dlouhý život jedné statické proměnné, a čísla z druhé řady se nepotkají s čísly z první řady.
		/// </summary>
		/// <returns></returns>
		protected static int _GetTemporaryId()
		{
			int nextTempId = 0;
			lock (_TemporaryLock)
			{
				if (_TemporaryId == Int32.MinValue)
					// Hlídání MinValue => čítač se nuluje:
					_TemporaryId = 0;
				nextTempId = --_TemporaryId;
			}
			return nextTempId;
		}
		/// <summary>
		/// Posledně použité ID - pro všechny potomky RecordCls
		/// </summary>
		protected static int _TemporaryId;
		/// <summary>
		/// Zámek pro přidělování hodnoty _TemporaryId
		/// </summary>
		protected static object _TemporaryLock = new object();
		#endregion
		#region STATIC METODY Library
		/// <summary>
		/// Vrátí textové vyjádření daného objektu (typicky: "REFER: NAZEV")
		/// Pokud je objekt null, vrátí číslo záznamu z druhého parametru: "(record=#12345)"
		/// Pokud je objekt null, a druhý parametr není zadán nebo je 0, vrátí: "(record = null)";
		/// </summary>
		/// <param name="data">Data o objektu, potomek této třídy</param>
		/// <returns>Textový popisek</returns>
		public static string ToString(RecordCls data)
		{
			return RecordCls.ToString(data, 0);
		}
		/// <summary>
		/// Vrátí textové vyjádření daného objektu (typicky: "REFER: NAZEV")
		/// Pokud je objekt null, vrátí číslo záznamu z druhého parametru: "(record=#12345)"
		/// Pokud je objekt null, a druhý parametr není zadán nebo je 0, vrátí: "(record = null)";
		/// </summary>
		/// <param name="data"></param>
		/// <param name="key"></param>
		/// <returns></returns>
		public static string ToString(RecordCls data, int key)
		{
			if (data != null)
				return data.ReferNazev;
			else if (key != 0)
				return "(record=#" + key.ToString() + ")";
			return "(record = null)";
		}
		#endregion
	}
	#endregion
	#region SubjectCls : konkrétní implementace RecordCls pro třídu 13: Obecný subjekt.
	/// <summary>
	/// Základní třída pro jednotlivý záznam třídy Obecný subjekt (lcs.subjekty)
	/// </summary>
	public class SubjectCls : RecordCls, IRecordKey, IPersistable, Noris.Schedule.Support.Sql.IDbRecord
	{
		#region KONSTRUKTOR
		/// <summary>
		/// Implicitní konstruktor, zajistí číslování záznamů do jednotné Temporary řady
		/// </summary>
		public SubjectCls()
		{ }
		/// <summary>
		/// Explicitní konstruktor, dovolí zvolit, zda se pro tento záznam bude generovat Temporary ID
		/// </summary>
		/// <param name="createTemporaryId">true pokud se pro záznam má vytvořit Temporary ID. Default (false) = nevytvoří se, bude mít RecordNumber = 0.</param>
		public SubjectCls(bool createTemporaryId)
			: base(createTemporaryId)
		{ }
		/// <summary>
		/// Číslo třídy: 13
		/// </summary>
		public override int ClassNumber { get { return ClassNr; } }
		/// <summary>Číslo třídy</summary>
		public const int ClassNr = 13;
		#endregion
		#region FILL FROM READER
		/// <summary>
		/// Naplní data do objektu this (systémové atributy: cislo_subjektu, cislo_nonsubjektu, cislo_objektu, cislo_poradace, reference_subjektu, nazev_subjektu)
		/// z předaného readeru za použití objektu mapování.
		/// Čtení touto metodou je cca 6x rychlejší než čtení generickou metodou.
		/// Metoda má vracet true, pokud převzala všechna data. 
		/// Je nanejvýše účelné, aby tak provedla, protože pokud vrátí false, pak se po jejím skončení provede generické ukládání dat (což je 6x pomalejší).
		/// </summary>
		/// <param name="reader">Vstupní DataReader</param>
		/// <param name="map">Mapování dat z readeru na fields tohoto objektu</param>
		/// <returns>true = data byla převzata / false = data nepřevzata</returns>
		public override bool FillFromReader(SqlDataReader reader, FieldMapperLoad map)
		{
			base.FillFromReader(reader, map);             // base naplní všechny systémové atributy, více jich není.
			return true;
		}
		#endregion
		#region SAVE RECORD
		/// <summary>
		/// Metoda, ve které datový objekt připraví podklady pro uložení svých dat do databáze.
		/// Objekt svoje proměnné vloží do exportní mapy prostřednictvím metod map.AddTable(); map.AddColumn();
		/// Pokud objekt chce plnit více tabulek, plní je do mapy postupně, vždy v pořadí {tabulka, sloupce tabulky; další tabulka; sloupce}.
		/// Po naplnění mapy se vrací true.
		/// </summary>
		/// <param name="map"></param>
		/// <returns></returns>
		public override bool PrepareSaveData(FieldMapperSave map)
		{
			SubjectCls._PrepareSaveData(this, map, "lcs.subjekty", true, 0, 0);
			return true;
		}
		/// <summary>
		/// Obecná metoda, která zajistí uložení dat subjektové třídy do tabulky subjektů 
		///  nebo do explicitně předané tabulky, která kopíruje systémové sloupce.
		/// Tedy: nadeklaruje zápis do tabulky lcs.subjekty nebo do explicitně zadané, a zajistí insert/update dat pro columns:
		/// reference_subjektu, nazev_subjektu, cislo_poradace, datum_vzniku, last_update, pachatel.
		/// Pro základní tabulku (lcs.subjekty) poté provede ukončení dat tabulky, pro explicitní tabulku ji nechá otevřenou.
		/// </summary>
		/// <param name="record">Předaný objekt, jehož data se mají zapsat</param>
		/// <param name="map">Mapa hodnot</param>
		public static void PrepareSaveData(RecordCls record, FieldMapperSave map)
		{
			SubjectCls._PrepareSaveData(record, map, "lcs.subjekty", true, 0, 0);
		}
		/// <summary>
		/// Obecná metoda, která zajistí uložení dat subjektové třídy do tabulky subjektů 
		///  nebo do explicitně předané tabulky, která kopíruje systémové sloupce.
		/// Tedy: nadeklaruje zápis do tabulky lcs.subjekty nebo do explicitně zadané, a zajistí insert/update dat pro columns:
		/// reference_subjektu, nazev_subjektu, cislo_poradace, datum_vzniku, last_update, pachatel.
		/// Pro základní tabulku (lcs.subjekty) poté provede ukončení dat tabulky, pro explicitní tabulku ji nechá otevřenou.
		/// </summary>
		/// <param name="record">Předaný objekt, jehož data se mají zapsat</param>
		/// <param name="map">Mapa hodnot</param>
		/// <param name="tableName">Jméno explicitní tabulky (včetně vlastníka lcs.), do které se mají uložit systémové sloupce subjektu.
		/// Metoda zajistí uložení dat systémových sloupců, a nechá tabulku "otevřenou" = následující metoda do této tabulky může ukládat další své sloupce.
		/// POZOR: tato varianta musí být volána na subjektových třídách, a jako druhá až po volání varianty bez jména tabulky (která ukládá data do lcs.subjekty).</param>
		public static void PrepareSaveData(RecordCls record, FieldMapperSave map, string tableName)
		{
			SubjectCls._PrepareSaveData(record, map, tableName, false, 0, 0);
		}
		/// <summary>
		/// Obecná metoda, která zajistí uložení dat subjektové třídy do tabulky subjektů 
		///  nebo do explicitně předané tabulky, která kopíruje systémové sloupce.
		/// Tedy: nadeklaruje zápis do tabulky lcs.subjekty nebo do explicitně zadané, a zajistí insert/update dat pro columns:
		/// reference_subjektu, nazev_subjektu, cislo_poradace, datum_vzniku, last_update, pachatel.
		/// Pro základní tabulku (lcs.subjekty) poté provede ukončení dat tabulky, pro explicitní tabulku ji nechá otevřenou.
		/// </summary>
		/// <param name="record">Předaný objekt, jehož data se mají zapsat</param>
		/// <param name="map">Mapa hodnot</param>
		/// <param name="tableName">Jméno explicitní tabulky (včetně vlastníka lcs.), do které se mají uložit systémové sloupce subjektu.
		/// Metoda zajistí uložení dat systémových sloupců, a nechá tabulku "otevřenou" = následující metoda do této tabulky může ukládat další své sloupce.
		/// POZOR: tato varianta musí být volána na subjektových třídách, a jako druhá až po volání varianty bez jména tabulky (která ukládá data do lcs.subjekty).</param>
		/// <param name="refMaxLen">Nejvyšší uložitelná délka stringu REFERENCE. 0 a záporné = bez omezení. Pro lcs.subjekty je fixně 30.</param>
		/// <param name="nazMaxLen">Nejvyšší uložitelná délka stringu NÁZEV. 0 a záporné = bez omezení. Pro lcs.subjekty je fixně 40.</param>
		public static void PrepareSaveData(RecordCls record, FieldMapperSave map, string tableName, int refMaxLen, int nazMaxLen)
		{
			SubjectCls._PrepareSaveData(record, map, tableName, false, refMaxLen, nazMaxLen);
		}
		/// <summary>
		/// Obecná metoda, která zajistí uložení dat subjektové třídy do tabulky subjektů.
		/// Tedy: nadeklaruje zápis do tabulky lcs.subjekty a zajistí insert/update dat pro columns:
		/// reference_subjektu, nazev_subjektu, cislo_poradace, datum_vzniku, last_update, pachatel.
		/// </summary>
		/// <param name="record">Předaný objekt, jehož data se mají zapsat</param>
		/// <param name="map">Mapa hodnot</param>
		/// <param name="tableName">Jméno tabulky</param>
		/// <param name="tableEnd">Požadavek na zakončení dat tabulky</param>
		/// <param name="refMaxLen">Nejvyšší uložitelná délka stringu REFERENCE. 0 a záporné = bez omezení. Pro lcs.subjekty je fixně 30.</param>
		/// <param name="nazMaxLen">Nejvyšší uložitelná délka stringu NÁZEV. 0 a záporné = bez omezení. Pro lcs.subjekty je fixně 40.</param>
		private static void _PrepareSaveData(RecordCls record, FieldMapperSave map, string tableName, bool tableEnd, int refMaxLen, int nazMaxLen)
		{
			/* 
			 * Tabulka lcs.subjekty obsahuje tato data:
			cislo_subjektu	reference_subjektu	nazev_subjektu	cislo_poradace	priznak_metatridy	datum_vzniku	last_update	pachatel	obdobi	timestamp
			
			 * Tabulka datová, pokud kopíruje systémové sloupce, obsahuje kopii těchto dat:
			cislo_subjektu	reference_subjektu	nazev_subjektu	cislo_poradace
 
			 */

			ClassInfo thisClass = Repository.Classes[record.ClassNumber];
			if (thisClass == null)
				Throw.SysError(MessageInfo.Get("Nelze ukládat subjektová data pro třídu %0, jde o neznámou třídu.", record.ClassNumber));
			if (!thisClass.IsSubject)
				Throw.SysError(MessageInfo.Get("Nelze ukládat subjektová data pro třídu %0 %1, nejde o subjektovou třídu.", record.ClassNumber, thisClass.NazevTridy));

			bool isLcsSubjekty = (tableName.ToLower().Trim() == "lcs.subjekty");

			// Ukládání dat subjektových tříd:
			// a) do tabulky "lcs.subjekty" i do tabulky Master, která kopíruje systémové atributy:
			map.AddTable(tableName, "cislo_subjektu");
			map.AddColumn("reference_subjektu", record.Reference, (isLcsSubjekty ? 30 : refMaxLen));
			map.AddColumn("nazev_subjektu", record.Nazev, (isLcsSubjekty ? 40 : nazMaxLen));
			// Číslo pořadače pouze při zakládání (insert), ne při úpravách (update):
			map.AddColumnForInsert("cislo_poradace", record.FolderNumber);
			if (isLcsSubjekty)
			{	// b) Pouze do tabulky lcs.subjekty:
				map.AddColumnForInsert("created_by", Steward.CurrentUser.RecordNumber);  // Nový vztah na zakladatele záznamu, jen při Insertu
				map.AddColumnForInsert("datum_vzniku", DateTime.Now);
				map.AddColumn("pachatel", Steward.CurrentUser.RecordNumber);
				map.AddColumn("last_update", DateTime.Now);
				map.AddColumn("obdobi", (Int64)0);
				// Neřeším sloupce :
				//   lcs.subjekty.priznak_metatridy, timestamp
			}
			if (tableEnd || isLcsSubjekty)
				map.AddTableEnd();
		}
		#endregion
		#region STATIC GENERÁTORY SELECTU
		/// <summary>
		/// SQL SELECT pro načtení columnů, odpovídajících této struktuře. Pozor: neobsahuje filtr ani order.
		/// </summary>
		public static new string SqlSelect { get { return "SELECT * FROM lcs.subjekty"; } }
		/// <summary>
		/// Jméno sloupce, který obsahuje klíč záznamu, určeno pro filtry.
		/// </summary>
		public static new string SqlRecord { get { return "cislo_subjektu"; } }
		/// <summary>
		/// Generátor SELECTU pro načítání do CACHE
		/// </summary>
		/// <returns></returns>
		public override string GetCacheSelect()
		{
			return SqlSelect + " WHERE " + SqlRecord;
		}
		#endregion
	}
	#endregion
	#region ARGUMENTY PRO HÁČKY V RecordCls
	/// <summary>
	/// Bázová třída pro argumenty, předávané do háčků RecordCls
	/// </summary>
	public class RecordClsEventArgs : EventArgs
	{
		/// <summary>
		/// Prázdný argument
		/// </summary>
		public static RecordClsEventArgs Empty { get { return new RecordClsEventArgs(); } }
	}
	#endregion
}
